/**
 * 
 */
package algo;

import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Serializable;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;

import porte.Client;
import porte.Porte;
import serveur_reso.Reso;

/**
 * @author BDRANY Akeel
 * @author BAYRAMOV Matin
 *
 */
public class AlgoHorloge implements Algorithm, Client {
	
	// Porte
	private Client porte;
	
	private int idporte;
	
	// Si est demandeur de section critique
	private boolean demandeurSC;
	
	// H - estampille locale 
	private int monEstampilleLocale;
	
	// HSC - estampille de demande d entree en SC
	private int monEstampilleHSC;
	
	// Nrel - nombre d'avis de liberation
	private int nombreRel;
	
	// X i - ensemble des portes dont envoi de l'avis de
	// liberation est differe
	private List<Integer> lesDemandeurs;
	
	// V voisinage de porte
	private List<Integer> voisinageV;
	
	
	private Horloge horloge;
	
	private int nombreDePlaceLibre;
	
	private PrintWriter trace;
	
	/**
	 * Les type de message à utiliser 
	 * lors d'envoi et reception des messages
	 */
	private enum TYPE_MESSAGE {
		ACK,
		ENTREE,
		SORTIR
	}
	
	/**
	 * Constructeur
	 */
	public AlgoHorloge(Client porte) {
		super();
		
		this.porte = porte;
		this.idporte = ((Porte) porte).getId();
		this.monEstampilleHSC = 0;
		this.monEstampilleLocale = 0;
		this.nombreRel = 0;
		this.demandeurSC = false;
		
		this.lesDemandeurs = new ArrayList<Integer>();
		this.voisinageV = new ArrayList<Integer>();
		
		this.nombreDePlaceLibre = Client.nombreDePlaceLibre;
		
		this.horloge = new Horloge();
				
		this.horloge.typeMessage = TYPE_MESSAGE.ACK;
		
		for (int i = 0; i < Client.nombreDePortes; i++) {
			if (this.idporte != i) {
				this.voisinageV.add(i);
			}
		}
		
		try {
			trace  = new PrintWriter(new FileWriter("trace_horloge_Porte_" + idporte));
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		trace(this.toString());
	}
	
	public synchronized void initialise() {
		
	}
	
	public void trace(String string) {
		trace.print(string);
		trace.flush();
	}

	@Override
	public void demandeEntree() throws RemoteException {
		trace("\nPORTE "+idporte+ " demande ENTREE");
		try {
			effectueDemandeEntree();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		trace("Fin demande entree\n\n"+this.toString());
	}

	@Override
	public void demandeSortie() throws RemoteException {
		trace("\nPORTE "+idporte+ " demande SORTIR");
		effectueDemandeSortir();
		trace("Fin demande sortir\n\n"+this.toString());
	}


	@Override
	public synchronized void receiveMessage(int from, int to, Serializable msg)
			throws RemoteException {
		
		Horloge horloge = (Horloge) msg;
		trace("reception message depuis "+from+" : "+horloge.toString());
		
		switch(horloge.typeMessage) {
		
			case ACK:
				acceptMessageACK();
			break;
			
			case ENTREE:
				acceptMessageEntree(horloge, from);	
			break;
			
			case SORTIR:
				acceptMessageSortir();
			break;
			
			default:
				trace("\nReceive message unknown typeMessage received");
			break;
		}
		
		trace("Fin receive message\n"+this.toString());
	}
	
	
	/**
	 * Accept le demande des section critique
	 * @throws InterruptedException 
	 */
	public synchronized void effectueDemandeEntree() throws InterruptedException {
				
		demandeurSC = true;
		monEstampilleHSC = monEstampilleLocale + 1;
		nombreRel = voisinageV.size();
		
		for(Integer voisin: voisinageV) {
			
			horloge.typeMessage = TYPE_MESSAGE.ENTREE;
			horloge.estampilleHSC = monEstampilleHSC;
		    
		    trace("envoyé demande à "+voisin+" avec "+horloge);
		    
		    try {
				this.getReso().sendMessage(idporte, voisin, horloge);
			} catch (RemoteException e) {
				e.printStackTrace();
			}
		}
		
		while (nombreRel != 0) {
			trace("\nAttends les "+this.nombreRel+" reponses\n");
			wait();
		}
		
		trace("AVANT liberation SC ");
		trace(this.toString());
		
		acceptLiberationSC();
		realiserEntree();
		
		trace("APRES liberation SC "+horloge+"\n");
		trace(this.toString());
		
	}
	
	
	/**
	 * Vérifie si une voiture peuvent entre ou pas
	 * Accepte et decrement le nombre de place libre dans le parking
	 * Refuse si il n'y a plus place libre dans le parking
	 */
	public synchronized void realiserEntree() {
		
		if (this.nombreDePlaceLibre == 0) {
			trace("Entree Voiture Refusee\n");
			trace("*========================*\n");
		}
		else {
			nombreDePlaceLibre--;
			trace("Entree Voiture Acceptee\n");
			trace("*========================*\n");
		}
	}
	

	
	/**
	 * Accept le message REQ(H) depuis j
	 * @param sonEstampilleHSC
	 * @param idporte
	 */
	public void acceptMessageEntree(Horloge horloge, int demandeur) {
		
		monEstampilleLocale = this.getMax(horloge.estampilleHSC, monEstampilleHSC) + 1;
		
		if (demandeurSC && ((monEstampilleHSC < horloge.estampilleHSC) || 
				((monEstampilleHSC == horloge.estampilleHSC) && (idporte < demandeur)))) {
			
			this.lesDemandeurs.add(demandeur);
		}
		else {
			
			horloge.typeMessage = TYPE_MESSAGE.ACK;
			
			try {
				trace("\nenvoyeé ACK à "+demandeur+" pour son demande ENTREE\n");
				this.getReso().sendMessage(idporte, demandeur, horloge);
			} catch (RemoteException e) {
				e.printStackTrace();
			}
			
			// Decrement le nombre de place dans le parking
			nombreDePlaceLibre--;
		}
	}
	

	
	
	/**
	 * Accept de la liberation de section critique
	 */
	public synchronized void acceptLiberationSC() {

		// On est plus dans section critique
		this.demandeurSC = false;
		
		// On va envoye un ACK a tout les demandeur
		if (this.lesDemandeurs.size() != 0) {
			
			horloge.typeMessage = TYPE_MESSAGE.ACK;
			
			// Sachant que les demandeur vont faire une entrée aussi
			// on decrement le nombre de place libre dans le parking
			if (nombreDePlaceLibre > 0) {
				nombreDePlaceLibre--;
			}
			
			for(Integer demandeur: lesDemandeurs) {
				
				trace("SEND MESSAGE to "+demandeur+" avec "+horloge);
				
				try {
					this.getReso().sendMessage(idporte, demandeur, horloge);
				} catch (RemoteException e) {
					e.printStackTrace();
				}
			}		
		}
		
		
		this.lesDemandeurs.clear();
		
	}
	
	
	
	
	
	/**
	 * Accepte le message sortie depuis une porte 
	 */
	public synchronized void effectueDemandeSortir() {
		
		// Increment le nombre de place libre dans le parking 
		nombreDePlaceLibre++;

		// Informe tous les autres de la sortie d'une voiture
		for(Integer voisin: voisinageV) {
			
			horloge.typeMessage = TYPE_MESSAGE.SORTIR;
		    trace("informe "+voisin+" d'une SORTIE\n "+horloge);
		    
		    try {
				getReso().sendMessage(idporte, voisin, horloge);
			} catch (RemoteException e) {
				e.printStackTrace();
			}
		}
	}
	
	
	/**
	 * Accepte le message ACK() depuis une porte
	 */
	public synchronized void acceptMessageACK() {
		
		nombreRel--;
		
		if (nombreRel == 0) {
			trace("\nTous les reponses recues\n");
			notifyAll();
		}
	}
	
	
	/**
	 * Accepte le message SORTIR depuis une porte
	 */
	public synchronized void acceptMessageSortir() {
		trace("Accept Message Sortir OK\n");
		nombreDePlaceLibre++;
	}
	
	/**
	 * Get max value of two integer
	 * @param a
	 * @param b
	 * @return
	 */
	public int getMax(int a, int b) {
		return (a >= b) ? a : b;
	}
	
	
	/**
	 * @return the porte
	 */
	public Client getPorte() {
		return porte;
	}
	
	/**
	 * @param porte the porte to set
	 */
	public void setPorte(Porte porte) {
		this.porte = porte;
	}

	/**
	 * @return the demandeurSC
	 */
	public boolean isDemandeurSC() {
		return demandeurSC;
	}

	/**
	 * @param demandeurSC the demandeurSC to set
	 */
	public void setDemandeurSC(boolean demandeurSC) {
		this.demandeurSC = demandeurSC;
	}

	/**
	 * @return the estampilleLocale
	 */
	public int getEstampilleLocale() {
		return monEstampilleLocale;
	}

	/**
	 * @param estampilleLocale the estampilleLocale to set
	 */
	public void setEstampilleLocale(int estampilleLocale) {
		this.monEstampilleLocale = estampilleLocale;
	}

	/**
	 * @return the estampilleHSC
	 */
	public int getEstampilleHSC() {
		return monEstampilleHSC;
	}

	/**
	 * @param estampilleHSC the estampilleHSC to set
	 */
	public void setEstampilleHSC(int estampilleHSC) {
		this.monEstampilleHSC = estampilleHSC;
	}

	/**
	 * @return the nombreRel
	 */
	public int getNombreRel() {
		return nombreRel;
	}

	/**
	 * @param nombreRel the nombreRel to set
	 */
	public void setNombreRel(int nombreRel) {
		this.nombreRel = nombreRel;
	}

	/**
	 * @return the lesDemandeurs
	 */
	public List<Integer> getLesDemandeurs() {
		return lesDemandeurs;
	}

	/**
	 * @param lesDemandeurs the lesDemandeurs to set
	 */
	public void setLesDemandeurs(List<Integer> lesDemandeurs) {
		this.lesDemandeurs = lesDemandeurs;
	}
	
	/**
	 * @return the voisinageV
	 */
	public List<Integer> getVoisinageV() {
		return voisinageV;
	}

	/**
	 * @param voisinageV the voisinageV to set
	 */
	public void setVoisinageV(List<Integer> voisinageV) {
		this.voisinageV = voisinageV;
	}
	
	/**
	 * 
	 * @return le reso de porte
	 */
	public Reso getReso() {
		return ((Porte) this.porte).getReso();
	}
	
	/**
	 * 
	 */
	public String toString() {
		
		StringBuilder string = new StringBuilder();
		
		string.append("=====================\n");
		
		string.append("ID:"+idporte+"\n");
		string.append("HSC:"+monEstampilleHSC+"\n");
		string.append("H:"+monEstampilleLocale+"\n");
		string.append("Nrel:"+nombreRel+"\n");
		string.append("Ri:"+demandeurSC+"\n");
		
		string.append("Demandeurs: ");
		for (Integer demandeur: lesDemandeurs) {
			string.append(demandeur+", ");	
		}
		string.append("\n");
		

		string.append("Voisins: ");
		for (Integer voisin: voisinageV) {
			string.append(voisin+", ");			
		}
		string.append("\n");
		
		string.append("NbDePlace: "+nombreDePlaceLibre+"\n");
		string.append("=====================\n");
		
		return string.toString();
	}

	/**
	 * @return the horloge
	 */
	public Horloge getHorloge() {
		return horloge;
	}

	/**
	 * @param horloge the horloge to set
	 */
	public void setHorloge(Horloge horloge) {
		this.horloge = horloge;
	}
	
	
	private static class Horloge implements java.io.Serializable {
		
		private static final long serialVersionUID = 1L;
		
		private int estampilleHSC;
		
		private TYPE_MESSAGE typeMessage;

		public String toString() {
			
			StringBuilder string = new StringBuilder();
			string.append("(");
			string.append("HSC: "+this.estampilleHSC+", ");
			string.append("TYPE: "+this.typeMessage);
			string.append(")");
			
			return string.toString();
		}
	}
}
